<?php

namespace app\controller;

use app\model\ResultModel;
use Carbon\Carbon;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use support\Cache;
use support\Log;
use support\Request;
use support\Response;
use Webman\RateLimiter\Annotation\RateLimiter;
use Workerman\Connection\TcpConnection;
use Workerman\Http\Client;
use Workerman\Protocols\Http\Chunk;

class CheckController
{
    public array $platform = [];

    public function __construct()
    {
        $this->platform = config('api_platforms.list');//所有的平台接口
    }

    /**
     * @param int $code
     * @param string $msg
     * @param array|null $data
     * @return false|string
     */
    private function _reData(int $code = 0, string $msg = 'success', array|null $data = null): bool|string
    {
        return json_encode([
            'code' => $code,
            'msg' => $msg,
            'data' => $data
        ]);
    }

    /**平台接口详细
     * @param string $name
     * @return array
     */
    private function _getPlatformInfo(string $name): array
    {
        $cacheKey = function (string $name) {
            return Str::slug($name . '-' . Carbon::now()->toDateString());
        };

        //指定某个平台的接口
        if (!empty($this->platform[$name]) && Cache::get($cacheKey($name), 1) <= ($this->platform[$name]['limit_daily'] ?? 100)) {
            return $this->platform[$name];
        }

        //自动分配平台
        foreach ($this->platform as $name => $item) {
            $tmpCacheKey = $cacheKey($name);

            if (Cache::get($tmpCacheKey, 1) <= ($item['limit_daily'] ?? 100)) return $item;
        }

        return [];
    }

    /**
     * @param TcpConnection $conn
     * @param string $content
     * @return void
     */
    private function _send(TcpConnection $conn, string $content): void
    {
        $conn->send(new Chunk($content));//主体内容
        $conn->send(new Chunk('')); // 发送空的的chunk代表response结束
    }

    /**
     * @param Request $request
     * @return Response
     * @throws \Throwable
     */
    #[RateLimiter(limit: 50, ttl: 60, message: '{"code":9998,"msg":"limited"}')]
    public function json(Request $request): Response
    {
        $ip = $request->input('ip');
        $cb = $request->input('cb');//回调方法名字（是否设置为jsonp格式）
        $platformName = $request->input('platform', 'ipapi.co');//指定数据平台
        $conn = $request->connection;//当前会话句柄

        //默认响应头
        $headers = [
            'x-request-time' => Carbon::now()->toDateTimeString(),
            'Content-Type' => 'application/json',
        ];

        //成功的数据格式
        $contentSuccess = function (array $data) use ($cb) {
            //jsonp格式
            if (!empty($cb)) {
                $content = sprintf('%s(%s)', $cb, json_encode($data));

            } else {//普通json格式
                $content = self::_reData(0, 'success', $data);
            }

            return $content;
        };

        //本地记录是否存在
        $record = ResultModel::where('ip', $ip)->first();
        if ($record) {
            $record = Arr::except($record->toArray(), ['id', 'source', 'created_at', 'updated_at']);
            $content = $contentSuccess($record);
            return response($content, 200, $headers);
        }

        //接口平台信息
        $platformInfo = self::_getPlatformInfo($platformName);
        if (empty($platformInfo)) return response(self::_reData(9999, '无法找到此平台'));

        $platformName = $platformInfo['name'] ?? '';//当前平台名称
        $platformUri = $platformInfo['uri'] ?? '';
        $platformRules = $platformInfo['rules'] ?? [];
        $platformRequest = sprintf($platformUri, $ip);

        try {
            //成功回调
            $success = function ($response) use ($platformName, $platformRules, $contentSuccess, $cb, $ip, $conn) {
                $body = $response->getBody();
                $status = $response->getStatusCode();
                Log::info('checking successful');

                if ($status !== 200) {
                    self::_send($conn, self::_reData(9999, '查询失败'));
                    return;
                };

                //处理数据
                $json = json_decode($body, true);
                $json = Arr::dot($json);

                //处理字段&入库
                try {
                    $data = array_map(
                        function ($remote) use ($json) {
                            return $json[$remote] ?? null;
                        },
                        $platformRules
                    );

                    //录入数据库
                    $model = new ResultModel();
                    $model->create(array_merge($data, ['ip' => $ip, 'source' => $platformName]));

                } catch (\Exception $e) {
                    Log::error($e->getMessage());
                    self::_send($conn, self::_reData(9999, '查询失败'));
                    return;
                }

                //响应到客户端
                $content = $contentSuccess($data);
                self::_send($conn, $content);
            };

            //失败回调
            $failed = function (\RuntimeException $exception) use ($conn) {
                Log::info($exception->getMessage());
                $body = self::_reData(9999, '请求失败，请重试!');

                //响应到客户端
                self::_send($conn, $body);
            };

            //异步请求
            $httpConf = [
                'max_conn_per_addr' => 128, // 每个域名最多维持多少并发连接
                'keepalive_timeout' => 15,  // 连接多长时间不通讯就关闭
                'connect_timeout' => 5,  // 连接超时时间
                'timeout' => 15,  // 请求发出后等待响应的超时时间
            ];
            $httpClient = new Client($httpConf);
            $httpClient->get($platformRequest, $success, $failed);

            //API调用次数累计
            $cacheKey = Str::slug($platformName . '-' . Carbon::now()->toDateString());
            $cacheRecord = Cache::get($cacheKey, 1);

            Cache::set($cacheKey, $cacheRecord + 1, 60 * 60 * 24);

        } catch (\Exception $e) {
            $body = self::_reData(9999, '操作失败');

            self::_send($conn, $body);//响应到客户端
        }

        //响应到客户端
        $headers = array_merge($headers, [
            'Transfer-Encoding' => 'chunked',//先给客户端返回一个带chunked的http头，然后将数据以chunk的方式发送给客户端。
        ]);

        return response()->withHeaders($headers);
    }

}
